/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace Xceed.Silverlight.Data
{
  [DebuggerNonUserCode]
  internal class AsyncResult : AsyncResultBase
  {
    #region Constructor

    internal AsyncResult( Guid callId, AsyncCallback userCallback, object asyncState )
      : base( callId )
    {
      m_userCallback = userCallback;
      m_asyncState = asyncState;
    }

    #endregion

    #region AsyncState Property

    public override object AsyncState
    {
      get
      {
        return m_asyncState;
      }
    }

    #endregion AsyncState Property

    #region LastResultData Property

    public object LastResultData
    {
      get;
      set;
    }

    #endregion LastResultData Property

    #region AsyncWaitHandle Property

    public override WaitHandle AsyncWaitHandle
    {
      get
      {
        if( m_waitHandle == null )
        {
          m_waitHandle = new ManualResetEvent( this.IsCompleted );
        }

        return m_waitHandle;
      }
    }

    #endregion AsyncWaitHandle Property

    #region CompletedSynchronously Property

    public override bool CompletedSynchronously
    {
      get
      {
        return m_completedSynchronously.GetValueOrDefault( false );
      }
    }

    #endregion CompletedSynchronously Property

    #region HasDependentCalls

    public bool HasDependentCalls
    {
      get
      {
        return ( m_dependentCalls.Count > 0 );
      }
    }

    #endregion HasDependentCalls

    #region IsCompleted Property

    public override bool IsCompleted
    {
      get
      {
        return ( m_dependentCalls.Count == 0 )
            && ( m_completedSynchronously.HasValue );
      }
    }

    #endregion IsCompleted Property

    #region LastIAsyncResult Property

    internal IAsyncResult LastIAsyncResult
    {
      get;
      private set;
    }

    #endregion LastIAsyncResult Property

    private static IAsyncResult BeginFakeAsyncCall( AsyncCallback callback, object asyncState )
    {
      CustomAsyncResult customAsyncResult = new CustomAsyncResult( Guid.Empty, asyncState );

      // By specifying "false" to the "synchronously" parameter, the completion will be
      // appended to the ui dispatcher event queue automaticaly, making this call asynchronous 
      // See AsyncResult.AsyncCallback.
      customAsyncResult.Complete( false, true );
      callback( customAsyncResult );

      return customAsyncResult;
    }

    private void AsyncCallback( IAsyncResult asyncResult )
    {
      // AsyncCallCompleted will be called by the async caller.
      if( asyncResult.CompletedSynchronously )
        return;

      this.InvokeAsyncCallCompletedFromUIThread( asyncResult );
    }

    private void AsyncCallCompleted( IAsyncResult asyncResult )
    {
      Debug.Assert( m_dependentCalls.Count != 0 );
      IAsyncResult currentAsyncResult = asyncResult;

      // This while will be handing the current callback, and 
      // every next AsyncCall that are executed synchronously
      // to avoid stack overflows.
      while( currentAsyncResult != null )
      {
        m_completedSynchronously = m_completedSynchronously.GetValueOrDefault( true ) && currentAsyncResult.CompletedSynchronously;
        DependentCallResult dependentResult = null;

        if( m_dependentCalls[ 0 ].IntermediateCallback != null )
        {
          dependentResult = m_dependentCalls[ 0 ].IntermediateCallback( this, currentAsyncResult );
        }

        if( dependentResult != null && dependentResult.NextCall != null )
        {
          m_dependentCalls[ 0 ] = dependentResult.NextCall;
        }
        else
        {
          m_dependentCalls.RemoveAt( 0 );
        }

        if( m_dependentCalls.Count > 0 )
        {
          object nextAsyncState = ( dependentResult != null ) ? dependentResult.ResultData : null;
          IAsyncResult nextAsyncResult = m_dependentCalls[ 0 ].AsyncCall( AsyncCallback, nextAsyncState );
          m_completedSynchronously = m_completedSynchronously.GetValueOrDefault( true ) && nextAsyncResult.CompletedSynchronously;
          currentAsyncResult = ( nextAsyncResult.CompletedSynchronously ) ? nextAsyncResult : null;
        }
        else
        {
          this.LastResultData = ( dependentResult != null ) ? dependentResult.ResultData : null;
          this.LastIAsyncResult = currentAsyncResult;
          currentAsyncResult = null;

          if( m_waitHandle != null )
          {
            m_waitHandle.Set();
          }

          this.FireCallback();
        }
      }
    }

    private void InvokeAsyncCallCompletedFromUIThread( IAsyncResult asyncResult )
    {
      UIDispatcher.Instance.InvokeFromUIThread( () => this.AsyncCallCompleted( asyncResult ) );
    }

    private void SetResult( object resultData, bool isSync )
    {
      if( m_dependentCalls.Count != 0 )
        throw new DataInternalException();

      m_completedSynchronously = isSync;
      this.LastResultData = resultData;

      if( m_waitHandle != null )
      {
        m_waitHandle.Set();
      }

      this.FireCallback();
    }

    internal void FireCallback()
    {
      if( m_userCallback != null )
      {
        m_userCallback( this );
      }
    }

    internal void AddDependentCall(
      Func<AsyncCallback, object, IAsyncResult> asyncCall,
      Func<AsyncResult, IAsyncResult, DependentCallResult> intermediateCallback )
    {
      this.AddDependentCall( new DependentCall( asyncCall, intermediateCallback ) );
    }

    internal void AddDependentCall( DependentCall dependentCall )
    {
      if( dependentCall == null )
        throw new ArgumentNullException( "dependentCall" );

      m_dependentCalls.Add( dependentCall );
    }

    internal void Start(
      Func<AsyncCallback, object, IAsyncResult> asyncCall,
      Func<AsyncResult, IAsyncResult, DependentCallResult> intermediateCallback )
    {
      this.Start( new DependentCall( asyncCall, intermediateCallback ) );
    }

    internal void Start( DependentCall dependentCall )
    {
      if( dependentCall == null )
        throw new ArgumentNullException( "dependentCall" );

      if( m_dependentCalls.Count != 0 )
        throw new DataInternalException();

      this.AddDependentCall( dependentCall );
      this.Start();
    }

    internal void Start()
    {
      if( m_dependentCalls.Count <= 0 )
        throw new DataInternalException();

      IAsyncResult asyncResult = m_dependentCalls[ 0 ].AsyncCall( AsyncCallback, null );
      //IAsyncResult asyncResult = m_dependentCalls[ 0 ].AsyncCall( AsyncCallback, this.LastResultData );

      if( asyncResult.CompletedSynchronously )
      {
        AsyncCallCompleted( asyncResult );
      }
    }

    internal void StartAsync( object resultData )
    {
      this.SetResult( resultData, false );
    }

    internal void Start( object resultData, bool runOnUIThread )
    {
      if( runOnUIThread )
      {
        this.Start(
          AsyncResult.BeginFakeAsyncCall,
          ( parentAr, ar ) => new DependentCallResult( resultData ) ); 
      }
      else
      {
        this.StartAsync( resultData );
      }
    }

    internal void StartSync( object resultData )
    {
      this.SetResult( resultData, true );
    }


    #region Private Fields

    private AsyncCallback m_userCallback;
    private object m_asyncState; //null;
    private Nullable<bool> m_completedSynchronously;
    private ManualResetEvent m_waitHandle; //null;
    private List<DependentCall> m_dependentCalls = new List<DependentCall>();

    #endregion Private Fields
  }
}
